home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 August: Tool Chest / Dev.CD Aug 00 TC Disk 1.toast / pc / sample code / files / standard file / standardgetfolder / standardgetfolder.c < prev    next >
Encoding:
Text File  |  2000-06-23  |  29.9 KB  |  929 lines

  1. /*
  2.     File:        StandardGetFolder.c
  3.  
  4.     Contains:    Routines needed to use CustomGetFile for selecting folders
  5.                  Also adds a button to create new folders.
  6.  
  7.     Written by: Andy Bachorski    
  8.  
  9.     Copyright:    Copyright © 1998-1999 by Apple Computer, Inc., All Rights Reserved.
  10.  
  11.                 You may incorporate this Apple sample source code into your program(s) without
  12.                 restriction. This Apple sample source code has been provided "AS IS" and the
  13.                 responsibility for its operation is yours. You are not permitted to redistribute
  14.                 this Apple sample source code as "Apple sample source code" after having made
  15.                 changes. If you're going to re-distribute the source, we require that you make
  16.                 it clear in the source that the code was descended from Apple sample source
  17.                 code, but that you've made changes.
  18.     
  19.     A Few Notes:
  20.                 The routines in this file have been tested, though not exhaustively so.
  21.                 They should not be used as the basis of the final code you use in your 
  22.                 application without further testing.
  23.     
  24.                 The error reporting is fairly complete, but there could be errors encounter 
  25.                 which deserve a more descriptive message than is displayed here.
  26.     
  27.                 This code assumes the presence of System 7 or later.  You should check
  28.                 the system version before using these routines in your code.
  29.     
  30.                 The MoreFiles package is used for some of the sanity checking in the routines,
  31.                 and is not included with this project.  You will need to obtain the 
  32.                 MoreFiles package (from the developer ftp site) to build an use this project.
  33.     
  34.                 The New Folder and 'the name is already taken' error dialogs are from the 
  35.                 Standard File package and are not included with this project, but rather are 
  36.                 loaded directly from the system.  If this makes you uncomfortable, you can
  37.                 always copy those dialog to your application before you ship.
  38.                 
  39.     Change History (most recent first):
  40.                 7/1/1999    Karl Groethe        Updated for Metrowerks Codewarror Pro 2.1
  41.                 
  42.                 2/02/99        Andy Bachorski        Updated project to CodeWarrior Pro 2
  43.                                                  Added New Folder button to dialog, along 
  44.                                                  with the routinesand changes to other 
  45.                                                  routines needed to support the button.
  46.                                                  One routine added is a filter to restrict
  47.                                                  the length of the new folder name and to 
  48.                                                  filter out ':' characters, which are 
  49.                                                  illegal in folder names.
  50.  
  51.                 
  52.  
  53. */
  54.  
  55. //******************    Universal Interfaces        ****************************
  56.  
  57. #include <Aliases.h>
  58. #include <Controls.h>
  59. #include <Folders.h>
  60. #include <Scrap.h>
  61. #include <StandardFile.h>
  62. #include <TextUtils.h>
  63. #include <ToolUtils.h>
  64.  
  65. #include "MoreFilesExtras.h"
  66.  
  67.  
  68. //******************    Project Interfaces            ****************************
  69.  
  70. #include "StandardGetFolder.h"
  71.  
  72.  
  73. //******************    Private Definitions            ****************************
  74.  
  75. enum {
  76.     kSFGetFolderDlgID        = 250,    //    ID for the CustomFile dialog resource
  77.     kSelectItem                = 10,     //    Item number for select button in this dialog
  78.     kCreateNewFolder        = 12,    //    Item number for new folder button
  79.     
  80.     //    Command-key characters should not be localized, so it is OK to hard-code them
  81.     //    directly where they are used.
  82.     
  83.     kSelectButtonKey        = 's',    //    Command key char for the select button
  84.     kNewFolderButton        = 'n'    //    Command key char for the new folder button
  85. };
  86.  
  87. enum {
  88.     kSelectButtonStrListID    = 250,    //    String list containing labels for select button
  89.     kSelectStrNum            = 1,    //    'Select: "^0"'
  90.     kDesktopStrNum            = 2,    //    'Desktop'
  91.     kSelectNoQuoteStrNum    = 3        //    'Select: ^0'
  92. };
  93.  
  94. enum {
  95.     kErrorStringListID        = 251,    //    String list containing error messages
  96.     errNameAlreadyTakenStr    = 1,    //    Name already taken message
  97.     errGenericIOErrorStr    = 2        //    Generic I/O error message, for unexpected errors.
  98. };
  99.  
  100. enum {
  101.     kSFGetFolderNameDlgID    = -6046,    //    These two dialogs are from the StandardFile package
  102.     kSFFolderNameTaken        = -6044,    //    and are loaded from the System file.  If this makes you nervous
  103.                                         //    you can always put a copy of them in your application.
  104.     kSFFolderNameTextItem    = 3,        //    Item number of name entry edit field in kSFGetFolderNameDlgID dialog
  105.     kSFNameTakenTextItem    = 2            //    Item number of error string in kSFFolderNameTaken dialog
  106. };
  107.  
  108. enum {
  109.     kDontUseQuotes            = false,    //    parameter for SetButtonName
  110.     kUseQuotes                = true
  111. };
  112.  
  113. enum {
  114.     kMaxStringLength        = 31,        //    Length limit for folder names
  115.     kButtonHiliteDelay        = 8            //    Ticks to delay while flashing buttons for command-key entries.
  116. };
  117.  
  118. enum {
  119.     kFilterAllTypes            = -1,        // pass all types to filter proc in StandardFild get dialog
  120.     kBootVolumeVRefNum        = -1
  121. };
  122.  
  123. //******************    Private Types                ****************************
  124.  
  125. //    This structure contains the data that StandardGetFolder needs to do its thing.
  126. //    It is passed to the standard file hook routine in the refcon when CustomGetFile is called.
  127.  
  128. struct UserDataRec {
  129.     DialogPtr            dialogPtr;                //    a pointer to the dialog (for access to items in the dialog).
  130.     StandardFileReply    *sfrPtr;                //    a pointer to the standard file reply record (so the current selection can be inspected).
  131.     FSSpec                oldSelectionFSSpec;        //    a copy of the "previous" file spec from the reply record (to check if the selection has changed).
  132.     long                desktopDirID;            //    the dirID 
  133.     short                desktopVRefNum;            //    and vRefNum for the desktop folder of the boot volume (to check if the desktop is selected).
  134. };
  135. typedef struct UserDataRec    UserDataRec;
  136. typedef UserDataRec            *UserDataRecPtr;
  137.  
  138.  
  139. //******************    Private Prototypes            ****************************
  140.  
  141. pascal    Boolean    SFGetFolderModalDialogFilter( DialogPtr dialogPtr, EventRecord *eventRecPtr, short *item, Ptr dataPtr );
  142. pascal    short    SFGetFolderDialogHook( short item, DialogPtr dialogPtr, Ptr dataPtr );
  143.  
  144.         void    CreateNewFolder( DialogPtr sfDlgPtr, UserDataRecPtr userDataRecPtr, short *dialogItem );
  145. pascal    Boolean    DialogFilter (DialogPtr inputDialog, EventRecord * dialogEventPtr, short *dialogItemPtr);
  146.         void    ShowCreateFolderError( Point dialogPt, OSErr anErr );
  147.         
  148.         void    SetSelectButtonName( DialogPtr dialogPtr, UserDataRecPtr userDataRecPtr );
  149.         void    SetButtonName( DialogPtr dialogPtr, short buttonID, StringPtr buttonName, Boolean quoteFlag);
  150.         void    GetLabelString( StringPtr theStr, short stringNum );
  151.         void    GetErrorString( StringPtr theStr, short stringNum );
  152.         void    CopyPStr( StringPtr src, StringPtr dest );
  153.         void    FlashButton( DialogPtr dialogPtr, short buttonID );
  154.         void    SetButtonEnabled( DialogPtr dialogPtr, short buttonID, Boolean enableButton );
  155.         Boolean    SameFSSpec( FSSpecPtr spec1, FSSpecPtr spec2 );
  156.         Boolean    FilterOutColons( long message );
  157.         Boolean    RestrictTextLength( DialogPtr dialogPtr, EventRecord * theDialogEvent, long maxStringLength, short dialogItem );
  158.         short    DialogHasSelectionRange( DialogPeek inputDialog );
  159.         Boolean    KeyIsEditKey( char theKey );
  160.         
  161.  
  162.  
  163. //******************************************************************************
  164. //                                                                                
  165. //    This routine assumes the presence of CustomGetFile, which is available in    
  166. //    System 7 and later.  You should check for the right system version before    
  167. //    calling this routine.                                                        
  168. //                                                                                
  169. pascal void StandardGetFolder( FileFilterYDUPP fileFilter, StandardFileReply *sfReply )
  170. {
  171.     Point                 dialogTopLeft = { -1, -1 };        //    Use to center the dialog.    
  172.     SFTypeList            mySFTypeList = { 0 };
  173.     UserDataRec            userData;
  174.     DlgHookYDUPP        dialogHookUPP;
  175.     ModalFilterYDUPP    modalFilterUPP;
  176.     OSErr                anErr;
  177.     
  178.     CopyPStr( "\p ", sfReply->sfFile.name );    //    Set initial contents of Select.    
  179.     
  180.     userData.sfrPtr = sfReply;                    //    Put point to in the reply record in the UserDataRec for later access.    
  181.     
  182.     //    Find the desktop folder, save it in the user data struct.    
  183.     
  184.     anErr = FindFolder( kBootVolumeVRefNum, kDesktopFolderType, kDontCreateFolder,
  185.                         &userData.desktopVRefNum, &userData.desktopDirID );
  186.     
  187.     if ( anErr != noErr )    //    Can't find desktop folder, use values that won't match any real vRefNum/dirID.    
  188.     {
  189.         userData.desktopVRefNum = 0;
  190.         userData.desktopDirID = 0;
  191.     }
  192.     
  193.     //    Display the custom choose folder dialog.    
  194.     
  195.     dialogHookUPP  = NewDlgHookYDProc( SFGetFolderDialogHook );                //    These should probably global and initialized at app        
  196.     modalFilterUPP = NewModalFilterYDProc( SFGetFolderModalDialogFilter );    //    startup time if this routine is call more than once.    
  197.     
  198.     CustomGetFile( fileFilter, kFilterAllTypes, mySFTypeList, sfReply, kSFGetFolderDlgID, 
  199.                    dialogTopLeft, dialogHookUPP, modalFilterUPP, nil, nil, &userData );
  200.                     
  201.     DisposeRoutineDescriptor( dialogHookUPP );
  202.     DisposeRoutineDescriptor( modalFilterUPP );
  203.     
  204.     //    The user has pressed the select button and no error was returned.    
  205.     //    Do a bit of post processing to ensure a useable result is returned.    
  206.     
  207.     if ( sfReply->sfGood )
  208.     {
  209.         //    If the selection's name is null, nothing is selected in the        
  210.         //    dialog list, so return the parent folder as the result.            
  211.         
  212.         if ( sfReply->sfFile.name[0] == '\0' )
  213.         {
  214.             FSSpec    parentDirFSS;
  215.             
  216.             anErr = FSMakeFSSpec( sfReply->sfFile.vRefNum, sfReply->sfFile.parID, "\p", &parentDirFSS );
  217.             if (anErr == noErr)
  218.             {
  219.                 sfReply->sfFile = parentDirFSS;
  220.             }
  221.             else
  222.             {
  223.                 sfReply->sfGood = false;    //    Can't get a valid FSSpec to return, so set the appropriate condition.    
  224.             }
  225.         }
  226.         
  227.         //    Everything still looks good, check what type of result is really being returned.
  228.         
  229.         if ( sfReply->sfGood )
  230.         {
  231.             Boolean        folderFlag;
  232.             Boolean        wasAliasedFlag;
  233.             
  234.             //    First, resolve alias files so the FSSpec points to the target of the alias.    
  235.             
  236.             anErr = ResolveAliasFile( &sfReply->sfFile, true, &folderFlag, &wasAliasedFlag );
  237.             if ( anErr != noErr )                                //    Alias couldn't be resolved, set the failure condition.
  238.             {
  239.                 sfReply->sfGood = false;
  240.             }
  241.             else if ( folderFlag )    //    Got a valid FSSpec, make sure the alias resolved to a folder.            
  242.             {
  243.                 //    Lastly, is the selection a volume?
  244.                 
  245.                 if ( sfReply->sfFile.parID == fsRtParID )    //    The parID of the root of a disk is always fsRtParID == 1
  246.                 {
  247.                     sfReply->sfIsVolume = true;
  248.                     sfReply->sfIsFolder = false;
  249.                 }
  250.                 else
  251.                 {
  252.                     sfReply->sfIsVolume = false;
  253.                     sfReply->sfIsFolder = true;
  254.                 }
  255.             }
  256.         }
  257.     }
  258. }//end StandardGetFolder
  259.  
  260. //******************************************************************************
  261. //                                                                                
  262. //    Return true (to filter the item) if this item is invisible or a file.    
  263. //                                                                                
  264. pascal Boolean OnlyVisibleFoldersCustomFileFilter( CInfoPBPtr myCInfoPBPtr, Ptr dataPtr )
  265. {
  266. #pragma unused (dataPtr)
  267.  
  268.     Boolean        visibleFlag;
  269.     Boolean        folderFlag;
  270.     
  271.     visibleFlag = ! ( myCInfoPBPtr->hFileInfo.ioFlFndrInfo.fdFlags & kIsInvisible );
  272.     folderFlag = ( myCInfoPBPtr->hFileInfo.ioFlAttrib & ioDirMask );
  273.     
  274.     //    Because the semantics of the filter proc are "true means don't show
  275.     //    it" we need to invert the result that we return
  276.     
  277.     return !( visibleFlag && folderFlag );
  278. }//end OnlyVisibleFoldersCustomFileFilter
  279.  
  280. //******************************************************************************
  281. //
  282. //    SFGetFolderModalDialogFilter maps a key to the Select button, and handles
  283. //    flashing of the button when the key is hit
  284. //
  285. pascal Boolean SFGetFolderModalDialogFilter( DialogPtr dialogPtr, EventRecord *eventRecPtr, short *item, Ptr dataPtr )
  286. {
  287. #pragma unused ( dataPtr )
  288.  
  289.     Boolean        eventHandled = false;
  290.     
  291.     //    It is always a good idea to make certain the proper dialog is showing,
  292.     //    since there are extension out there that can muck with things, and also
  293.     //    because standard file can nest dialogs but calls the same filter for each.
  294.     
  295.     if ( ((WindowPeek)dialogPtr)->refCon == sfMainDialogRefCon )
  296.     {
  297.         //    Check if the select button was hit
  298.         
  299.         if ( ( eventRecPtr->what == keyDown )  &&  ( eventRecPtr->modifiers & cmdKey ) )
  300.         {
  301.             if ( (eventRecPtr->message & charCodeMask) == kSelectButtonKey )
  302.             {
  303.                 *item = kSelectItem;
  304.                 FlashButton( dialogPtr, kSelectItem );
  305.                 eventHandled = true;
  306.             }
  307.             else if ( (eventRecPtr->message & charCodeMask) == kNewFolderButton )
  308.             {
  309.                 *item = kCreateNewFolder;
  310.                 FlashButton( dialogPtr, kCreateNewFolder );
  311.                 eventHandled = true;
  312.             }
  313.         }
  314.     }
  315.         
  316.     return eventHandled;
  317. }//end SFGetFolderModalDialogFilter
  318.  
  319. //******************************************************************************
  320. //                                                                                
  321. //    The StandardFile hook routine.  It ...                                                    
  322. //        maps the select button to Open                                            
  323. //        presents the new folder dialog                                            
  324. //        sets the Select button name                                                
  325. //                                                                                
  326. pascal short SFGetFolderDialogHook( short dialogItem, DialogPtr dialogPtr, Ptr dataPtr )
  327. {
  328.     //    Be sure Std File is really showing us the intended dialog, not a nested modal dialog.    
  329.     
  330.     if ( ((WindowPeek)dialogPtr)->refCon == sfMainDialogRefCon )
  331.     {
  332.         UserDataRecPtr    userDataRecPtr = (UserDataRecPtr)dataPtr;
  333.         
  334.         //    Map the Select button to Open    
  335.         
  336.         switch ( dialogItem )
  337.         {
  338.             case kSelectItem :
  339.             {
  340.                 dialogItem = sfItemOpenButton;
  341.             }
  342.             break;
  343.             
  344.             case sfHookNullEvent :
  345.                 if ( SameFSSpec( &userDataRecPtr->sfrPtr->sfFile, &userDataRecPtr->oldSelectionFSSpec ) )
  346.                 {
  347.                     break;
  348.                 }
  349.  
  350.             case sfItemFileListUser :
  351.             {
  352.                 OSErr        anErr;
  353.                 Boolean        enableNewFolderButton = true;
  354.                 SInt8        ioACUser;
  355.                 
  356.                 //    Is the volume locked?    
  357.                 anErr = CheckVolLock( nil, userDataRecPtr->sfrPtr->sfFile.vRefNum );
  358.                 if ( anErr != noErr )
  359.                 {
  360.                     enableNewFolderButton = false;    //    It's locked, no need to go further.
  361.                 }
  362.                 else    //    Volume not locked, does it have access restrictions?    
  363.                 {
  364.                     anErr = GetIOACUser( userDataRecPtr->sfrPtr->sfFile.vRefNum, userDataRecPtr->sfrPtr->sfFile.parID,
  365.                                          userDataRecPtr->sfrPtr->sfFile.name, &ioACUser );
  366.                     if ( anErr == noErr )
  367.                     {
  368.                         if ( ! userHasFullAccess( ioACUser ) )
  369.                         {
  370.                             enableNewFolderButton = false;
  371.                         }
  372.                     }
  373.                 }
  374.                 SetButtonEnabled( dialogPtr, kCreateNewFolder, enableNewFolderButton );
  375.             }
  376.             
  377.             case sfHookFirstCall :
  378.             case sfHookChangeSelection :
  379.             case sfHookRebuildList :
  380.                 SetSelectButtonName( dialogPtr, userDataRecPtr );
  381.             break;
  382.             
  383.             case kCreateNewFolder :    //    The New Folder button has been clicked.  Do that new folder dialog 'thang.    
  384.                 CreateNewFolder( dialogPtr, userDataRecPtr, &dialogItem );
  385.             break;
  386.             
  387.             default :    //    This case is here for debugging purposes only
  388.                         //     - provides a place to catch unexpected selectors while debugging.    
  389.                 if ( dialogItem != sfHookNullEvent )
  390.                 {
  391.                     dialogItem = dialogItem;
  392.                 }
  393.             break;
  394.         }
  395.         
  396.         //    Save the current selection as the old selection for comparison next time.    
  397.         //
  398.         //    It's not valid on the first call, though, or if we don't have a 
  399.         //    name available from standard file.
  400.         
  401.         if ( dialogItem != sfHookFirstCall || userDataRecPtr->sfrPtr->sfFile.name[0] != '\0')
  402.         {
  403.             userDataRecPtr->oldSelectionFSSpec = userDataRecPtr->sfrPtr->sfFile;
  404.         }
  405.         else
  406.         {
  407.             //    On first call, empty string won't set the button correctly, so invalidate oldSelection.    
  408.             
  409.             userDataRecPtr->oldSelectionFSSpec.vRefNum = 999;
  410.             userDataRecPtr->oldSelectionFSSpec.parID = 0;
  411.         }
  412.     }
  413.     
  414.     return dialogItem;
  415. }
  416.  
  417. //******************************************************************************
  418.  
  419. enum {
  420.     kHorzOffsetFromNewFolderButtonPos    = 55,
  421.     kVertOffsetFromNewFolderButtonPos    = 35
  422. };
  423.  
  424. void CreateNewFolder( DialogPtr sfDlgPtr, UserDataRecPtr userDataRecPtr, short *dialogItem )
  425. {
  426.     DialogPtr    dialogPtr;
  427.     Point        itemPoint;
  428.     OSErr        anErr;
  429.     
  430.     dialogPtr = GetNewDialog( kSFGetFolderNameDlgID, nil, (WindowPtr)(-1) );    //    -1 means put the dialog in front of all other windows.    
  431.     if ( dialogPtr != nil )
  432.     {
  433.         Rect        itemRect;
  434.         short        itemType;
  435.         Handle        itemHandle;
  436.         
  437.         ModalFilterUPP    modalFilterUPP = NewModalFilterProc( DialogFilter );
  438.         SInt16    hitItem = 0;
  439.  
  440.         //    Set up our user items for various things.    
  441.         SetDialogDefaultItem( dialogPtr, ok );
  442.         SetDialogCancelItem( dialogPtr, cancel );
  443.         SetDialogTracksCursor( dialogPtr, true );
  444.         
  445.         //    Get the position of the New Folder button so the new folder dialog can be    
  446.         //    centered there, which more or less matches the default system behavior.        
  447.         GetDialogItem( sfDlgPtr, kCreateNewFolder, &itemType, &itemHandle, &itemRect );
  448.         
  449.         itemPoint.h = itemRect.left - kHorzOffsetFromNewFolderButtonPos;
  450.         itemPoint.v = itemRect.top - kVertOffsetFromNewFolderButtonPos;
  451.         LocalToGlobal( &itemPoint );
  452.         
  453.         MoveWindow( (WindowPtr)dialogPtr, itemPoint.h, itemPoint.v, true );
  454.         
  455.         //    Select the default folder name.
  456.         SelectDialogItemText( dialogPtr, kSFFolderNameTextItem, 0, 32 );
  457.         
  458.         //    Let's see the dialog now.
  459.         ShowWindow( (WindowPtr)dialogPtr );
  460.         DrawDialog( dialogPtr );
  461.         
  462.         //    Spin in the dialog 'till one of the buttons is pressed.
  463.         do {
  464.             ModalDialog( modalFilterUPP, &hitItem );
  465.         } while ( hitItem != ok  &&  hitItem != cancel );
  466.         
  467.         if ( hitItem == ok )
  468.         {
  469.             Str31    itemStr;
  470.             
  471.             GetDialogItem( dialogPtr, kSFFolderNameTextItem, &itemType, &itemHandle, &itemRect );
  472.             GetDialogItemText( itemHandle, itemStr );
  473.             
  474.             //    Make sure the user has entered a name string to work with.    
  475.             if ( itemStr[ 0 ] > '\0' )
  476.             {
  477.                 long    newDirID;
  478.                 
  479.                 //    Check to see if the item selected is on the desktop.  If it is, create the new folder on the desktop.
  480.                 
  481.                 if ( userDataRecPtr->sfrPtr->sfFile.parID == fsRtParID )
  482.                 {
  483.                     userDataRecPtr->sfrPtr->sfFile.parID = userDataRecPtr->desktopDirID;
  484.                     userDataRecPtr->sfrPtr->sfFile.vRefNum = userDataRecPtr->desktopVRefNum;
  485.                 }
  486.                 
  487.                 BlockMoveData( itemStr, userDataRecPtr->sfrPtr->sfFile.name, itemStr[ 0 ] + 1 );
  488.                 
  489.                 anErr = FSpDirCreate( &userDataRecPtr->sfrPtr->sfFile, smSystemScript, &newDirID );
  490.                 userDataRecPtr->sfrPtr->sfFile.parID = newDirID;
  491.                 if ( anErr == noErr )
  492.                 {
  493.                     *dialogItem = sfHookChangeSelection;    //    Force a list update
  494.                 }
  495.             }
  496.         }
  497.         DisposeDialog( dialogPtr );
  498.         InitCursor();        //    Init to prevent I-beam from hanging around in some circumstances.    
  499.         DisposeRoutineDescriptor( modalFilterUPP );
  500.     }
  501.     if ( anErr != noErr )
  502.     {
  503.         ShowCreateFolderError( itemPoint, anErr );
  504.     }
  505.     
  506.     return;
  507. }//end CreateNewFolder
  508.  
  509. //******************************************************************************
  510.  
  511. pascal Boolean DialogFilter( DialogPtr inputDialog, EventRecord * dialogEventPtr, short *dialogItemPtr )
  512. {
  513.     Boolean            wasAKey;
  514.     Boolean            eventWasHandled = false;
  515.     
  516.     //    Get some values here that will be used through-out the filter.
  517.     
  518.     wasAKey = ( (dialogEventPtr->what == keyDown)  ||  (dialogEventPtr->what == autoKey) );
  519.     
  520.     //------------------------------------------------------------------------------
  521.     //    First some filtering on key events.                                            
  522.     //------------------------------------------------------------------------------
  523.     
  524.     if ( wasAKey )
  525.     {
  526.         eventWasHandled = FilterOutColons( dialogEventPtr->message );
  527.         if ( eventWasHandled == false )
  528.         {
  529.             eventWasHandled = RestrictTextLength( inputDialog, dialogEventPtr, kMaxStringLength, kSFFolderNameTextItem );
  530.         }
  531.     }
  532.     
  533.     //    One final thing to do is to call the standard dialog filter.                 
  534.     //    You MUST call the standard filter if you want any of the standard dialog    
  535.     //    behaviors to happen.  The OK button border, cursor tracking, and the rest    
  536.     //    ONLY happen if you call the standard dialog filter.                            
  537.     //    Also, the standard dialog filter is only called if returnValue is still        
  538.     //    false by the time we get here.                                                    
  539.     
  540.     if ( eventWasHandled == false )
  541.     {
  542.         ModalFilterUPP    theModalProc;
  543.         OSErr            myErr;
  544.         
  545.         myErr = GetStdFilterProc( &theModalProc );
  546.         if ( myErr == noErr )
  547.         {
  548.             eventWasHandled = CallModalFilterProc( theModalProc, inputDialog, dialogEventPtr, dialogItemPtr );
  549.         }
  550.     }
  551.         
  552.     return eventWasHandled;
  553. }//    end DialogFilter
  554.  
  555. //******************************************************************************
  556.  
  557. enum {
  558.     kHorzOffsetFromNewFolderDialogPos    = 14,
  559.     kVertOffsetFromNewFolderDialogPos    = 6
  560. };
  561.  
  562. void ShowCreateFolderError( Point dialogPt, OSErr anErr )
  563. {
  564.     DialogPtr    dialogPtr;
  565.     Str255        errStr;
  566.     short        errStrIndex;
  567.     
  568.     if ( anErr == dupFNErr )    //    That name is already taken
  569.     {
  570.         errStrIndex = errNameAlreadyTakenStr;
  571.     }
  572.     else    //    An unexpected error while creating the folder, so give a generic message.
  573.     {
  574.         errStrIndex = errGenericIOErrorStr;
  575.     }
  576.     
  577.     dialogPtr = GetNewDialog( kSFFolderNameTaken, nil, (WindowPtr)(-1) );    //    -1 means put the dialog in front of all other windows.    
  578.     if ( dialogPtr != nil )
  579.     {
  580.         Rect        itemRect;
  581.         short        itemType;
  582.         Handle        itemHandle;
  583.         SInt16        hitItem = 0;
  584.  
  585.         //    Set up our user items for various things.    
  586.         SetDialogDefaultItem( dialogPtr, ok );
  587.         SetDialogTracksCursor( dialogPtr, true );
  588.         
  589.         //    Position the dialog so it's shown in the same location as the name entry dialog was.    
  590.         MoveWindow( (WindowPtr)dialogPtr, dialogPt.h - kHorzOffsetFromNewFolderDialogPos, dialogPt.v - kVertOffsetFromNewFolderDialogPos, true );
  591.         
  592.         //    Put our error message in the dialog's text item.
  593.         GetErrorString( errStr, errStrIndex );
  594.         GetDialogItem( dialogPtr, kSFNameTakenTextItem, &itemType, &itemHandle, &itemRect );
  595.         SetDialogItemText( itemHandle, errStr );
  596.         
  597.         //    Let's see the dialog now.    
  598.         ShowWindow( (WindowPtr)dialogPtr );
  599.         DrawDialog( dialogPtr );
  600.         
  601.         ModalDialog( nil, &hitItem );
  602.         
  603.         DisposeDialog( dialogPtr );
  604.     }
  605.     
  606.     return;
  607. }//end ShowCreateFolderError
  608.  
  609. //******************************************************************************
  610.  
  611. void SetSelectButtonName( DialogPtr dialogPtr, UserDataRecPtr userDataRecPtr )
  612. {
  613.     //    Be sure there is a file name selected.    
  614.     
  615.     if ( userDataRecPtr->sfrPtr->sfFile.name[ 0 ] != '\0' )
  616.     {
  617.         SetButtonName( dialogPtr, kSelectItem,
  618.                        userDataRecPtr->sfrPtr->sfFile.name, kUseQuotes );
  619.     }
  620.     else    //    No file name in the reply, so...
  621.     {
  622.         //    Is the desktop selected?    
  623.         
  624.         if (    userDataRecPtr->sfrPtr->sfFile.vRefNum == userDataRecPtr->desktopVRefNum
  625.             &&    userDataRecPtr->sfrPtr->sfFile.parID   == userDataRecPtr->desktopDirID )
  626.         {
  627.             //    Set button to "Select Desktop".        
  628.             
  629.             Str63        desktopName;
  630.             
  631.             GetLabelString( desktopName, kDesktopStrNum );
  632.             SetButtonName( dialogPtr, kSelectItem, desktopName, kDontUseQuotes );
  633.         }
  634.         else    //    Desktop not selected, so there is nothing in the folder selected.    
  635.         {        //    Get parent directory's name for the Select button.
  636.                 //    Passing an empty name string to FSMakeFSSpec gets the name of the folder specified by the parID parameter
  637.                         
  638.             FSSpec    parentFSS;
  639.             
  640.             
  641.             (void) FSMakeFSSpec( userDataRecPtr->sfrPtr->sfFile.vRefNum,
  642.                                  userDataRecPtr->sfrPtr->sfFile.parID, "\p", &parentFSS );
  643.             SetButtonName( dialogPtr, kSelectItem, parentFSS.name, kUseQuotes );
  644.         }
  645.     }
  646. }//end SetSelectButtonName
  647.  
  648. //******************************************************************************
  649. //
  650. //    SetButtonName sets the name of the Select button in the SF dialog.
  651. //
  652. //    To do this, we need to call the Script Manager to truncate the label in the    
  653. //    middle to fit the button and to merge the button name with the word Select    
  654. //    (possibly followed by quotes).  Using the Script Manager avoids all sorts    
  655. //    of problems internationally.
  656. //
  657. //    buttonName is the name to appear following the word Select
  658. //    quoteFlag should be true if the name is to appear in quotes
  659. //
  660. void SetButtonName( DialogPtr dialogPtr, short buttonID, StringPtr buttonName, Boolean quoteFlag)
  661. {
  662.     short     buttonType;
  663.     Handle    buttonHandle;
  664.     Rect    buttonRect;
  665.     Handle    labelHandle;
  666.     Str255    labelStr;
  667.     OSErr    anErr;
  668.     
  669.     //    Get the details of the button from the dialog
  670.     
  671.     GetDialogItem( dialogPtr, buttonID, &buttonType, &buttonHandle, &buttonRect );
  672.     
  673.     //    Get the string for the select button label, "Select ^0" or "Select “^0”"
  674.     
  675.     if ( quoteFlag == kUseQuotes )
  676.     {
  677.         GetLabelString( labelStr, kSelectStrNum );
  678.     }
  679.     else
  680.     {
  681.         GetLabelString( labelStr, kSelectNoQuoteStrNum );
  682.     }
  683.     
  684.     //    Make string handles containing the select button label and the
  685.     //    file name to be stuffed into the button
  686.     
  687.     anErr = PtrToHand( &labelStr[1], &labelHandle, labelStr[0] );
  688.     if ( anErr == noErr )
  689.     {
  690.         Handle    nameHandle = nil;
  691.         short    textWidth;
  692.         
  693.         // cut out the middle of the file name to fit the button
  694.         //
  695.         // we'll temporarily use labelStr here to hold the modified button name
  696.         // since we don't own the buttonName string storage space
  697.  
  698.         textWidth = ( buttonRect.right - buttonRect.left ) - StringWidth( labelStr );
  699.  
  700.         CopyPStr( buttonName, labelStr );
  701.         (void) TruncString( textWidth, labelStr, smTruncMiddle );
  702.  
  703.         anErr = PtrToHand( &labelStr[1], &nameHandle, labelStr[0] );
  704.         if ( anErr == noErr )
  705.         {
  706.             Str15    keyStr;
  707.             
  708.             // replace the ^0 in the Select string with the file name
  709.  
  710.             CopyPStr( "\p^0", keyStr );
  711.  
  712.             (void) ReplaceText( labelHandle, nameHandle, keyStr );
  713.  
  714.             labelStr[0] = (unsigned char)GetHandleSize( labelHandle );
  715.             BlockMoveData( *labelHandle, &labelStr[1], labelStr[0] );
  716.  
  717.             // now set the control title, and re-validate the area
  718.             // above the control to avoid a needless redraw
  719.  
  720.             SetControlTitle( (ControlHandle)buttonHandle, labelStr );
  721.  
  722.             ValidRect( &buttonRect );
  723.             
  724.             DisposeHandle( nameHandle );
  725.         }
  726.         DisposeHandle( labelHandle );
  727.     }
  728.     
  729.     return;
  730. }//end SetButtonName
  731.  
  732. //******************************************************************************
  733.  
  734. void GetLabelString( StringPtr theStr, short stringNum )
  735. {
  736.     GetIndString( theStr, kSelectButtonStrListID, stringNum );
  737. }
  738.  
  739. //******************************************************************************
  740.  
  741. void GetErrorString( StringPtr theStr, short stringNum )
  742. {
  743.     GetIndString( theStr, kErrorStringListID, stringNum );
  744. }
  745.  
  746. //******************************************************************************
  747.  
  748. void CopyPStr( StringPtr src, StringPtr dest )
  749. {
  750.     BlockMoveData( src, dest, 1 + src[ 0 ] );
  751. }
  752.  
  753. //******************************************************************************
  754. //
  755. // FlashButton briefly highlights the dialog button 
  756. // as feedback for key equivalents
  757. //
  758. void FlashButton( DialogPtr dialogPtr, short buttonID )
  759. {
  760.     short    buttonType;
  761.     Handle    buttonHandle;
  762.     Rect    buttonRect;
  763.     UInt32    finalTicks;
  764.     
  765.     GetDialogItem( dialogPtr, buttonID, &buttonType, &buttonHandle, &buttonRect );
  766.     
  767.     HiliteControl((ControlHandle) buttonHandle, kControlButtonPart);
  768.     Delay( kButtonHiliteDelay, &finalTicks );
  769.     HiliteControl((ControlHandle) buttonHandle, 0);
  770.     
  771.     return;
  772. }//end FlashButton
  773.  
  774. //******************************************************************************
  775. //
  776. void SetButtonEnabled( DialogPtr dialogPtr, short buttonID, Boolean enableButton )
  777. {
  778.     short    buttonType;
  779.     Handle    buttonHandle;
  780.     Rect    buttonRect;
  781.     
  782.     GetDialogItem( dialogPtr, buttonID, &buttonType, &buttonHandle, &buttonRect );
  783.     
  784.     if ( enableButton )
  785.     {
  786.         HiliteControl( (ControlHandle)buttonHandle, kControlNoPart );
  787.     }
  788.     else
  789.     {
  790.         HiliteControl( (ControlHandle)buttonHandle, kControlInactivePart );
  791.     }
  792.  
  793.     return;
  794. }//end SetButtonEnabled
  795.  
  796. //******************************************************************************
  797.  
  798. Boolean SameFSSpec( FSSpecPtr spec1, FSSpecPtr spec2 )
  799. {
  800.     return (   spec1->vRefNum == spec2->vRefNum
  801.             && spec1->parID == spec2->parID
  802.             && EqualString( spec1->name, spec2->name, false, false ) );
  803. }//end SameFSSpec
  804.  
  805. //******************************************************************************
  806. //                                                                                
  807. //    This key filter demonstrates how to disallow certain characters.            
  808. //    This particular filter won't allow the entry of numeric characters.            
  809. //    Any number entered will be eaten, and a beep generated.                        
  810. //                                                                                
  811. Boolean    FilterOutColons( long message )
  812. {
  813.     char        theKeyPressed = message & charCodeMask;
  814.     Boolean        keyWasFiltered = false;
  815.     
  816.     if ( theKeyPressed == ':' )
  817.     {
  818.         //    Dang, it's a colon, reject it.     
  819.         keyWasFiltered = true;
  820.     }
  821.     
  822.     return keyWasFiltered;
  823. }//    end FilterOutColons
  824.  
  825. //**************************************************************************************
  826. //                                                                                        
  827. //    This key filter restricts the length of text in an edit field.                        
  828. //                                                                                        
  829. //    The paste command needs to be handled here, since it can cause the text in the edit    
  830. //    field to go over the length limit.  
  831. //    This filter also ckecks for and passed through keyboard navigation keys in the case where
  832. //    the edit field is at its maximum length.    
  833. //
  834. Boolean RestrictTextLength( DialogPtr dialogPtr, EventRecord * theDialogEvent, long maxStringLength, short dialogItem )
  835. {
  836.     Rect        itemRect;
  837.     Handle        itemHandle;
  838.     Str255        itemStr;
  839.     
  840.     short        itemType;
  841.     short        selectionLength;
  842.     char        theKeyPressed;
  843.     
  844.     Boolean        keyWasFiltered = false;
  845.     
  846.     selectionLength = DialogHasSelectionRange( (DialogPeek)dialogPtr );
  847.         
  848.     //    Get the text from the edit field.                                    
  849.     GetDialogItem( dialogPtr, dialogItem, &itemType, &itemHandle, &itemRect );
  850.     GetDialogItemText( itemHandle, itemStr );
  851.     
  852.     theKeyPressed = theDialogEvent->message & charCodeMask;
  853.     
  854.     if (theDialogEvent->modifiers & cmdKey)                    //    Was the key pressed a command?                    
  855.     {
  856.         
  857.         if ( (theKeyPressed == 'v')  ||  (theKeyPressed == 'V') )    //    Was it a paste command? ( ** NOT LOCALIZED ** )    
  858.         {
  859.             long    offset;
  860.             long    scrapLen = GetScrap( nil, 'TEXT', &offset );
  861.             long    newStrLen = itemStr[0] + (scrapLen - selectionLength);
  862.             
  863.             if ( newStrLen > maxStringLength )        //    Yes, will the paste exceed the max length?        
  864.             {
  865.                 keyWasFiltered = true;
  866.             }
  867.         }
  868.     }
  869.     else if ( ! KeyIsEditKey( theKeyPressed )  &&  (itemStr[0] - selectionLength + 1) > maxStringLength )
  870.     {
  871.         keyWasFiltered = true;
  872.     }
  873.     
  874.     return keyWasFiltered;
  875. }//    end RestrictTextLength
  876.  
  877. //******************************************************************************
  878.  
  879. short DialogHasSelectionRange( DialogPeek inputDialog )
  880. {
  881.     TEHandle    theTERecord;
  882.     short        selectionRange;
  883.     
  884.     theTERecord = inputDialog->textH;
  885.     selectionRange = (*theTERecord)->selEnd - (*theTERecord)->selStart;
  886.     
  887.     return ( selectionRange );
  888. }//    end DialogHasSelectionRange
  889.  
  890. //******************************************************************************
  891. //                                                                                
  892. //    A little utility to see if the current key is an edit-type key                
  893. //                                                                                
  894. //    key equates 
  895. enum
  896. {
  897.     kEnterKey            = 0x03,
  898.     kTabKey                = 0x09,
  899.     kReturnKey            = 0x0D,
  900.     kBackSpace            = 0x08,
  901.     kEscKey                = 0x1B,
  902.     kLeftArrow            = 0x1C,
  903.     kRightArrow            = 0x1D,
  904.     kUpArrow            = 0x1E,
  905.     kDownArrow            = 0x1F,
  906.     kDeleteKey            = 0x7F
  907. };
  908.  
  909. Boolean KeyIsEditKey( char theKey )
  910. {
  911.     long        index;
  912.     Boolean        isEditKey = false;
  913.     
  914.     char        editChars[] = { kLeftArrow, kUpArrow, kRightArrow, kDownArrow, kBackSpace, kEscKey, kTabKey, kEnterKey };
  915.     long        editCharCount = sizeof( editChars ) / sizeof( char );
  916.     
  917.     for ( index = 0; index < editCharCount; index++ )
  918.     {
  919.         if ( theKey == editChars[ index ] )
  920.         {
  921.             isEditKey = true;
  922.         }
  923.     }
  924.  
  925.     return (isEditKey);
  926. }//    end KeyIsEditKey
  927.  
  928. //******************************************************************************
  929.